home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr05 / ddeexamp.zip / STDDDE.C < prev    next >
C/C++ Source or Header  |  1993-07-08  |  75KB  |  3,115 lines

  1. /*
  2.     stddde.c
  3.  
  4.     This module implements basic DDE support for an application.
  5.     It supports the system topic and allows topics and items to
  6.     be dynamically added and removed while the application is running.
  7.     It only supports CF_TEXT for system items.
  8.  
  9.     It assumes that there is only one service provided and that
  10.     the server is never too busy to process a command.
  11.  
  12.     All objects created by this module are created using _fcalloc
  13.     which allows efficient use of global memory and zero initializes
  14.     each one.
  15.  
  16.     The DDE Execute command parser is only invoked if the topic
  17.     receiving the execute command has no generic execute function
  18.     associated with it.  This is different from item processing which
  19.     defaults to the generic topic handler if there is no specific item 
  20.     handler.
  21.  
  22. */
  23.  
  24. #include <windows.h>
  25. #include "stddde.h"
  26. #include <malloc.h>
  27. #include <string.h>
  28. #include <ctype.h>
  29.  
  30. //
  31. // Local constants
  32. //
  33.  
  34. #define MAXFORMATS      128     // max no of CF formats we will list
  35. #define MAXOPS  ((UINT)128)     // max no of opcodes we can execute
  36. #define MAXRESULTSIZE   256     // largest result string returned
  37.  
  38. //
  39. // Define the pointer type we use in the exec cmd op table
  40. //
  41.  
  42. typedef void FAR *POP;
  43. typedef POP FAR *PPOP;
  44.  
  45. //
  46. // Local data 
  47. //
  48.  
  49. static DDESERVERINFO ServerInfo;
  50.  
  51. //
  52. // Format lists
  53. //
  54.  
  55. static WORD SysFormatList[] = {
  56.     CF_TEXT,
  57.     NULL};
  58.  
  59. //
  60. // Standard format name lookup table
  61. //
  62.  
  63. CFTAGNAME CFNames[] = {
  64.     CF_TEXT,        SZCF_TEXT,       
  65.     CF_BITMAP,      SZCF_BITMAP,     
  66.     CF_METAFILEPICT,SZCF_METAFILEPICT,
  67.     CF_SYLK,        SZCF_SYLK,       
  68.     CF_DIF,         SZCF_DIF,        
  69.     CF_TIFF,        SZCF_TIFF,       
  70.     CF_OEMTEXT,     SZCF_OEMTEXT,    
  71.     CF_DIB,         SZCF_DIB,        
  72.     CF_PALETTE,     SZCF_PALETTE,    
  73.     CF_PENDATA,     SZCF_PENDATA,    
  74.     CF_RIFF,        SZCF_RIFF,       
  75.     CF_WAVE,        SZCF_WAVE,       
  76.     NULL,           NULL
  77.     };
  78.  
  79. //
  80. // Local functions
  81. //
  82.  
  83. HDDEDATA SysReqTopics(UINT wFmt, HSZ hszTopic, HSZ hszItem);
  84. HDDEDATA SysReqItems(UINT wFmt, HSZ hszTopic, HSZ hszItem);
  85. HDDEDATA SysReqFormats(UINT wFmt, HSZ hszTopic, HSZ hszItem);
  86. HDDEDATA SysReqProtocols(UINT wFmt, HSZ hszTopic, HSZ hszItem);
  87. HDDEDATA SysReqResultInfo(UINT wFmt, HSZ hszTopic, HSZ hszItem);
  88. HDDEDATA TopicReqFormats(UINT wFmt, HSZ hszTopic, HSZ hszItem);
  89. HDDEDATA FAR PASCAL __export StdDDECallback(WORD wType,
  90.                                  WORD wFmt,
  91.                                  HCONV hConv,
  92.                                  HSZ hsz1,
  93.                                  HSZ hsz2,
  94.                                  HDDEDATA hData,
  95.                                  DWORD dwData1,
  96.                                  DWORD dwData2);
  97. HDDEDATA DoWildConnect(HSZ hszTopic);
  98. BOOL DoCallback(WORD wType,
  99.                 WORD wFmt,
  100.                 HCONV hConv,
  101.                 HSZ hsz1,
  102.                 HSZ hsz2,
  103.                 HDDEDATA hData,
  104.                 HDDEDATA *phReturnData);
  105. HDDEDATA MakeDataFromFormatList(LPWORD pFmt, WORD wFmt, HSZ hszItem);
  106. void AddFormatsToList(LPWORD pMain, int iMax, LPWORD pList);
  107. static BOOL AddConversation(HCONV hConv, HSZ hszTopic);
  108. static BOOL RemoveConversation(HCONV hConv, HSZ hszTopic);
  109. static PDDECONVINFO FindConversation(HSZ hszTopic);
  110. static LPSTR SkipWhiteSpace(LPSTR pszString);
  111. static BOOL ProcessExecRequest(PDDETOPICINFO pTopic, HDDEDATA hData);
  112. static BOOL ParseCmd(LPSTR FAR *ppszCmdLine,
  113.                      PDDETOPICINFO pTopic,
  114.                      LPSTR pszError,
  115.                      UINT uiErrorSize,
  116.                      PPOP pOpTable,
  117.                      UINT uiNops,
  118.                      LPSTR pArgBuf);
  119. static LPSTR ScanForChar(char c, LPSTR *ppStr);
  120. static PDDEEXECCMDFNINFO ScanForCommand(PDDEEXECCMDFNINFO pInfoTable, LPSTR *ppStr);
  121. static LPSTR ScanForString(LPSTR *ppStr, LPSTR pszTerm, LPSTR *ppArgBuf);
  122. static BOOL IsValidStringChar(char c);
  123. static BOOL FAR SysResultExecCmd(PDDETOPICINFO pTopic,
  124.                                  LPSTR pszResult, 
  125.                                  UINT uiResultSize, 
  126.                                  UINT uiNargs, 
  127.                                  LPSTR FAR *ppArgs);
  128.  
  129. /*
  130.     @doc EXTERNAL
  131.  
  132.     @api BOOL | InitializeDDE | Initialize all the DDE lists for
  133.         this server and initialize DDEML.
  134.  
  135.     @parm HANDLE | hInstance | Instance handle of the calling application.
  136.  
  137.     @parm LPSTR  | pszServiceName | Pointer to a string containing
  138.         the name of the DDE service.
  139.  
  140.     @parm LPDWORD | pdwDDEInst | A pointer to a DWORD variable to receive
  141.         the instance identifier returned by DDEML. This argument is optional.
  142.         It may be set to NULL.
  143.  
  144.     @parm PFNCALLBACK | pfnCustomCallback | A pointer to an optional
  145.         custom callback function.  This parameter may be set to NULL in
  146.         which case all the handling will be done by stddde.
  147.  
  148.     @rdesc The return value is TRUE if the initialization succeeds and
  149.         FALSE if ti fails.
  150.  
  151.     @comm A number of items are added to the system topic by this
  152.         function which are supported transparently to the application.
  153.  
  154. */
  155.  
  156. BOOL InitializeDDE(HANDLE hInstance,
  157.                    LPSTR lpszServiceName,
  158.                    LPDWORD lpdwDDEInst,
  159.                    PFNCALLBACK lpfnCustomCallback,
  160.                    DWORD dwFilterFlags)
  161. {
  162.     UINT uiResult;
  163.  
  164.     //
  165.     // Make a proc instance for the standard callback
  166.     //
  167.  
  168.     ServerInfo.pfnStdCallback = 
  169.                 (PFNCALLBACK) MakeProcInstance((FARPROC) StdDDECallback,
  170.                                                hInstance);
  171.  
  172.     //
  173.     // Make sure the application hasn't requested any filter options 
  174.     // which will prevent us from working correctly.
  175.     //
  176.  
  177.     dwFilterFlags &= !( CBF_FAIL_CONNECTIONS 
  178.                       | CBF_SKIP_CONNECT_CONFIRMS
  179.                       | CBF_SKIP_DISCONNECTS);
  180.  
  181.     //
  182.     // Initialize DDEML.  Note that DDEML doesn't make any callbacks
  183.     // during initialization so we don't need to worry about the
  184.     // custom callback yet.
  185.     //
  186.  
  187.     uiResult = DdeInitialize(&ServerInfo.dwDDEInstance,
  188.                              ServerInfo.pfnStdCallback,
  189.                              dwFilterFlags,
  190.                              0);
  191.  
  192.     if (uiResult != DMLERR_NO_ERROR) return FALSE;
  193.  
  194.     //
  195.     // make a proc instance for the custom callback if there is one
  196.     //
  197.  
  198.     if (lpfnCustomCallback) {
  199.  
  200.         ServerInfo.pfnCustomCallback = 
  201.                 (PFNCALLBACK) MakeProcInstance((FARPROC) lpfnCustomCallback,
  202.                                                hInstance);
  203.     }
  204.  
  205.     //
  206.     // Return the DDE instance id if it was requested
  207.     //
  208.  
  209.     if (lpdwDDEInst) {
  210.  
  211.         *lpdwDDEInst = ServerInfo.dwDDEInstance;
  212.     }
  213.  
  214.     //
  215.     // Copy the service name and create a DDE name handle for it
  216.     //
  217.  
  218.     ServerInfo.lpszServiceName = lpszServiceName;
  219.     ServerInfo.hszServiceName = DdeCreateStringHandle(ServerInfo.dwDDEInstance,
  220.                                                       lpszServiceName,
  221.                                                       CP_WINANSI);
  222.  
  223.     //
  224.     // Add all the system topic items to the service tree
  225.     //
  226.  
  227.     AddDDEItem(SZDDESYS_TOPIC,
  228.                SZDDESYS_ITEM_TOPICS,
  229.                SysFormatList,
  230.                SysReqTopics,
  231.                NULL);
  232.  
  233.     AddDDEItem(SZDDESYS_TOPIC,
  234.                SZDDESYS_ITEM_SYSITEMS,
  235.                SysFormatList,
  236.                SysReqItems,
  237.                NULL);
  238.  
  239.     AddDDEItem(SZDDESYS_TOPIC,
  240.                SZDDE_ITEM_ITEMLIST,
  241.                SysFormatList,
  242.                SysReqItems,
  243.                NULL);
  244.  
  245.     AddDDEItem(SZDDESYS_TOPIC,
  246.                SZDDESYS_ITEM_FORMATS,
  247.                SysFormatList,
  248.                SysReqFormats,
  249.                NULL);
  250.  
  251.     AddDDEItem(SZDDESYS_TOPIC, 
  252.                SZ_PROTOCOLS,
  253.                SysFormatList,
  254.                SysReqProtocols,
  255.                NULL);
  256.  
  257.  
  258.     //
  259.     // Register the name of our service
  260.     //
  261.  
  262.     DdeNameService(ServerInfo.dwDDEInstance, 
  263.                    ServerInfo.hszServiceName,
  264.                    NULL,
  265.                    DNS_REGISTER);
  266.  
  267.     return TRUE;
  268. }
  269.  
  270. /*
  271.     @doc EXTERNAL
  272.  
  273.     @api void | UninitializeDDE | Terminate use of DDEML.
  274.  
  275.     @rdesc There is no return value.
  276.  
  277.     @comm Any resources used by earlier calls are freed.
  278.  
  279. */
  280.  
  281. void UninitializeDDE(void)
  282. {
  283.     PDDETOPICINFO pTopic;
  284.     PDDEITEMINFO pItem;
  285.  
  286.     //
  287.     // Unregister the service name
  288.     //
  289.  
  290.     DdeNameService(ServerInfo.dwDDEInstance, 
  291.                    ServerInfo.hszServiceName,
  292.                    NULL,
  293.                    DNS_UNREGISTER);
  294.  
  295.     //
  296.     // free the name handle
  297.     //
  298.  
  299.     DdeFreeStringHandle(ServerInfo.dwDDEInstance, ServerInfo.hszServiceName);
  300.  
  301.     //
  302.     // Walk the server topic list, freeing all the other string handles
  303.     //
  304.  
  305.     pTopic = ServerInfo.pTopicList;
  306.     while (pTopic) {
  307.  
  308.         DdeFreeStringHandle(ServerInfo.dwDDEInstance, pTopic->hszTopicName);
  309.         pTopic->hszTopicName = NULL;
  310.  
  311.         //
  312.         // Free any item handles it owns
  313.         //
  314.  
  315.         pItem = pTopic->pItemList;
  316.         while (pItem) {
  317.  
  318.             DdeFreeStringHandle(ServerInfo.dwDDEInstance, pItem->hszItemName);
  319.             pItem->hszItemName = NULL;
  320.  
  321.             pItem = pItem->pNext;
  322.         }
  323.  
  324.         pTopic = pTopic->pNext;
  325.     }
  326.  
  327.     //
  328.     // Release DDEML
  329.     //
  330.  
  331.     DdeUninitialize(ServerInfo.dwDDEInstance);
  332.     ServerInfo.dwDDEInstance = NULL;
  333.  
  334.     //
  335.     // Free any proc instances we created
  336.     //
  337.  
  338.     if (ServerInfo.pfnCustomCallback) {
  339.         FreeProcInstance((FARPROC)ServerInfo.pfnCustomCallback);
  340.         ServerInfo.pfnCustomCallback = NULL;
  341.     }
  342.  
  343.     FreeProcInstance((FARPROC)ServerInfo.pfnStdCallback);
  344.     ServerInfo.pfnStdCallback = NULL;
  345.  
  346. }
  347.  
  348. /*
  349.     @doc EXTERNAL
  350.  
  351.     @api PDDETOPICINFO | FindTopicFromName | Find a topic in the topic list 
  352.         by searching for its string name.
  353.  
  354.     @parm LPSTR | pszName | A pointer to a string containing the name
  355.         to search for.
  356.  
  357.     @rdesc The return value is a pointer to a DDETOPICINFO structure if
  358.         the topic is found, otherwise it is NULL.
  359.  
  360.     @comm The search is not case sensitive.
  361.  
  362. */
  363.  
  364. PDDETOPICINFO FindTopicFromName(LPSTR lpszName)
  365. {
  366.     PDDETOPICINFO pTopic;
  367.  
  368.     pTopic = ServerInfo.pTopicList;
  369.     while (pTopic) {
  370.  
  371.         if (lstrcmpi(pTopic->pszTopicName, lpszName) == 0) {
  372.             break;
  373.         }
  374.  
  375.         pTopic = pTopic->pNext;
  376.     }
  377.  
  378.     return pTopic;
  379. }
  380.  
  381. /*
  382.     @doc EXTERNAL
  383.  
  384.     @api PDDETOPICINFO | FindTopicFromHsz | Find a topic in the topics list
  385.         by searching for its HSZ value.
  386.  
  387.     @parm HSZ | hszName | The HSZ value to search for.
  388.  
  389.     @rdesc The return value is a pointer to a DDETOPICINFO structure if
  390.         the topic is found, otherwise it is NULL.
  391.  
  392.     @comm The search is not case sensitive.
  393.  
  394. */
  395.  
  396. PDDETOPICINFO FindTopicFromHsz(HSZ hszName)
  397. {
  398.     PDDETOPICINFO pTopic;
  399.  
  400.     pTopic = ServerInfo.pTopicList;
  401.     while (pTopic) {
  402.  
  403.         if (DdeCmpStringHandles(pTopic->hszTopicName, hszName) == 0) {
  404.             break;
  405.         }
  406.  
  407.         pTopic = pTopic->pNext;
  408.     }
  409.  
  410.     return pTopic;
  411. }
  412.  
  413. /*
  414.     @doc EXTERNAL
  415.  
  416.     @api PDDEITEMINFO | FindItemFromName | Find an item in the items
  417.         list of a given topic by searching for its string name.
  418.  
  419.     @parm PDDETOPICINFO | pTopic | Pointer to the DDETOPICINFO structure
  420.         for the topic being searched.
  421.  
  422.     @parm LPSTR | pszItem | Pointer to the string containing the name
  423.         of the item to search for.
  424.  
  425.     @rdesc The return value is a pointer to a DDEITEMINFO structure
  426.         if the item is found, otherwise it is NULL.
  427.  
  428.     @comm The search is not case sensitive.
  429.  
  430. */
  431.  
  432. PDDEITEMINFO FindItemFromName(PDDETOPICINFO pTopic, LPSTR lpszItem)
  433. {
  434.     PDDEITEMINFO pItem;
  435.  
  436.     pItem = pTopic->pItemList;
  437.     while (pItem) {
  438.  
  439.         if (lstrcmpi(pItem->pszItemName, lpszItem) == 0) {
  440.             break;
  441.         }
  442.  
  443.         pItem = pItem->pNext;
  444.     }
  445.  
  446.     return pItem;
  447. }
  448.  
  449. /*
  450.     @doc EXTERNAL
  451.  
  452.     @api PDDEITEMINFO | FindItemFromHsz | Find an item in the items
  453.         list of a given topic by searching for its HSZ name.
  454.  
  455.     @parm PDDETOPICINFO | pTopic | Pointer to the DDETOPICINFO structure
  456.         for the topic being searched.
  457.  
  458.     @parm LPSTR | hszItem | HSZ of the item to search for.
  459.  
  460.     @rdesc The return value is a pointer to a DDEITEMINFO structure
  461.         if the item is found, otherwise it is NULL.
  462.  
  463.     @comm The search is not case sensitive.
  464.  
  465. */
  466.  
  467. PDDEITEMINFO FindItemFromHsz(PDDETOPICINFO pTopic, HSZ hszItem)
  468. {
  469.     PDDEITEMINFO pItem;
  470.  
  471.     pItem = pTopic->pItemList;
  472.     while (pItem) {
  473.  
  474.         if (DdeCmpStringHandles(pItem->hszItemName, hszItem) == 0) {
  475.             break;
  476.         }
  477.  
  478.         pItem = pItem->pNext;
  479.     }
  480.  
  481.     return pItem;
  482. }
  483.  
  484. /*
  485.     @doc EXTERNAL
  486.  
  487.     @api PDDEEXECCMDFNINFO | FindExecCmdFromName | Find a DDE execute command
  488.         from its string name.
  489.  
  490.     @parm PDDETOPICINFO | pTopic | Pointer to the DDETOPICINFO structure
  491.         for the topic being searched.
  492.  
  493.     @parm LPSTR | pszCmd | Pointer to the string containing the name
  494.         of the command to search for.
  495.  
  496.     @rdesc The return value is a pointer to a DDEEXECCMDFNINFO structure
  497.         if the command is found, otherwise it is NULL.
  498.  
  499.     @comm The search is not case sensitive.
  500.  
  501. */
  502.  
  503. PDDEEXECCMDFNINFO FindExecCmdFromName(PDDETOPICINFO pTopic, LPSTR pszCmd)
  504. {
  505.     PDDEEXECCMDFNINFO pCmd;
  506.  
  507.     pCmd = pTopic->pCmdList;
  508.     while (pCmd) {
  509.  
  510.         if (lstrcmpi(pCmd->pszCmdName, pszCmd) == 0) {
  511.             break;
  512.         }
  513.  
  514.         pCmd = pCmd->pNext;
  515.     }
  516.  
  517.     return pCmd;
  518. }
  519.  
  520. /*
  521.     @doc EXTERNAL
  522.  
  523.     @api PDDETOPICINFO | AddDDETopic | Add a new topic and default processing
  524.         for its item list and formats.
  525.  
  526.     @parm LPSTR | pszTopic | Pointer to a string containing the name
  527.         of the topic.
  528.  
  529.     @parm PDDEEXECFN | pfnExec | Pointer to an optional execute command
  530.         processing function.  This argument may be NULL in which case
  531.         the standard execute command parser will be used to process the
  532.         request.
  533.  
  534.     @parm PDDEREQUESTFN | pfnRequest | Pointer to an optional function
  535.         to handle request requests for items of this topic.  If this 
  536.         function is provided it will be called for any item not having its
  537.         own request function processor.
  538.  
  539.     @parm PDDEPOKEFN | pfnPoke | Pointer to an optional function
  540.         to handle poke requests for items of this topic.  If this 
  541.         function is provided it will be called for any item not having its
  542.         own poke function processor.
  543.  
  544.     @rdesc The return value is a pointer to a DDETOPICINFO structure
  545.         if the topic is added or NULL if not.
  546.  
  547.     @comm If the topic already exists, its information will be updated
  548.         by the call.
  549.  
  550. */
  551.  
  552. PDDETOPICINFO AddDDETopic(LPSTR lpszTopic,
  553.                           PDDEEXECFN pfnExec,
  554.                           PDDEREQUESTFN pfnRequest,
  555.                           PDDEPOKEFN pfnPoke)   
  556. {
  557.     PDDETOPICINFO pTopic;
  558.  
  559.     //
  560.     // See if we already have this topic
  561.     //
  562.  
  563.     pTopic = FindTopicFromName(lpszTopic);
  564.  
  565.     if (pTopic) {
  566.  
  567.         //
  568.         // We already have this one so just update its info
  569.         //
  570.  
  571.         pTopic->pfnExec = pfnExec;
  572.         pTopic->pfnRequest = pfnRequest;
  573.         pTopic->pfnPoke = pfnPoke;
  574.  
  575.     } else {
  576.  
  577.         //
  578.         // Create a new topic
  579.         //
  580.  
  581.         pTopic = _fcalloc(1, sizeof(DDETOPICINFO));
  582.         if (!pTopic) return NULL;
  583.  
  584.         //
  585.         // Fill out the info
  586.         //
  587.  
  588.         pTopic->pszTopicName = lpszTopic;
  589.         pTopic->hszTopicName = DdeCreateStringHandle(ServerInfo.dwDDEInstance,
  590.                                                     lpszTopic,
  591.                                                     CP_WINANSI);
  592.         pTopic->pItemList = NULL;
  593.         pTopic->pfnExec = pfnExec;
  594.         pTopic->pfnRequest = pfnRequest;
  595.         pTopic->pfnPoke = pfnPoke;
  596.         pTopic->pCmdList = NULL;
  597.  
  598.         //
  599.         // Add it to the list
  600.         //
  601.  
  602.         pTopic->pNext = ServerInfo.pTopicList;
  603.         ServerInfo.pTopicList = pTopic;
  604.  
  605.         //
  606.         // Add handlers for its item list and formats.
  607.         //
  608.  
  609.         AddDDEItem(lpszTopic,
  610.                    SZDDE_ITEM_ITEMLIST,
  611.                    SysFormatList,
  612.                    SysReqItems,
  613.                    NULL);
  614.  
  615.         AddDDEItem(lpszTopic,
  616.                    SZDDESYS_ITEM_FORMATS,
  617.                    SysFormatList,
  618.                    TopicReqFormats,
  619.                    NULL);
  620.  
  621.     }
  622.  
  623.     return pTopic;
  624. }
  625.  
  626. /*
  627.     @doc EXTERNAL
  628.  
  629.     @api BOOL | RemoveDDETopic | Remove a topic from the topics list.
  630.  
  631.     @parm LPSTR | pszTopic | Pointer to a string containing the 
  632.         name of the topic.
  633.  
  634.     @rdesc The return value is TRUE if the topic is removed, FALSE
  635.         if not.
  636.  
  637.     @comm If there is a conversation active on this topic it
  638.         will be disconnected. Removing a topic removes all its
  639.         associated items.
  640.  
  641. */
  642.  
  643. BOOL RemoveDDETopic(LPSTR lpszTopic)
  644. {
  645.     PDDETOPICINFO pTopic, pPrevTopic;
  646.     PDDECONVINFO pCI;
  647.  
  648.     //
  649.     // See if we have this topic by walking the list
  650.     //
  651.  
  652.     pPrevTopic = NULL;
  653.     pTopic = ServerInfo.pTopicList;
  654.     while (pTopic) {
  655.  
  656.         if (lstrcmpi(pTopic->pszTopicName, lpszTopic) == 0) {
  657.  
  658.             //
  659.             // Found it. Disconnect any active conversations on this topic
  660.             //
  661.  
  662.             while (pCI = FindConversation(pTopic->hszTopicName)) {
  663.  
  664.                 //
  665.                 // Tell DDEML to disconnect it
  666.                 //
  667.  
  668.                 DdeDisconnect(pCI->hConv);
  669.  
  670.                 //
  671.                 // We don't get notified until later that it's gone
  672.                 // so remove it from the list now so we won't keep
  673.                 // finding it in this loop.
  674.                 // When DDEML sends the disconnect notification later
  675.                 // it won't be there to remove but that doesn't matter.
  676.                 //
  677.  
  678.                 RemoveConversation(pCI->hConv, pCI->hszTopicName);
  679.  
  680.             }
  681.  
  682.             //
  683.             // Remove all the execute command handlers in the topic
  684.             //
  685.  
  686.             while(pTopic->pCmdList) {
  687.  
  688.                 if (!RemoveDDEExecCmd(lpszTopic,
  689.                                       pTopic->pCmdList->pszCmdName)) {
  690.                     return FALSE; // some error
  691.                 }
  692.             }
  693.  
  694.             //
  695.             // Free all the items in the topic
  696.             //
  697.         
  698.             while(pTopic->pItemList) {
  699.         
  700.                 if (!RemoveDDEItem(lpszTopic, 
  701.                                    pTopic->pItemList->pszItemName)) {
  702.                     return FALSE; // some error
  703.                 }    
  704.             }
  705.         
  706.             //
  707.             // Unlink it from the list.
  708.             //
  709.  
  710.             if (pPrevTopic) {
  711.  
  712.                 pPrevTopic->pNext = pTopic->pNext;
  713.  
  714.             } else {
  715.  
  716.                 ServerInfo.pTopicList = pTopic->pNext;
  717.  
  718.             }
  719.  
  720.             //
  721.             // Release its string handle
  722.             //
  723.         
  724.             DdeFreeStringHandle(ServerInfo.dwDDEInstance,
  725.                                 pTopic->hszTopicName);
  726.         
  727.             //
  728.             // Free the memory associated with it
  729.             //
  730.         
  731.             _ffree(pTopic);
  732.         
  733.             return TRUE;
  734.  
  735.         }
  736.  
  737.         pTopic = pTopic->pNext;
  738.     }
  739.  
  740.     //
  741.     // We don't have this topic
  742.     //
  743.  
  744.     return FALSE;
  745. }
  746.  
  747. /*
  748.     @doc EXTERNAL
  749.  
  750.     @api PDDEITEMINFO | AddDDEItem | Add an item to a topic.
  751.  
  752.     @parm LPSTR | pszTopic | Pointer to a string containing the
  753.         name of the topic to add the item to.
  754.  
  755.     @parm LPSTR | pszItem | Pointer to a string containing the
  756.         name of the item to add.
  757.  
  758.     @parm LPWORD | pFormatList | Pointer to a null terminated 
  759.         list of valid clipboard format ids.
  760.  
  761.     @parm PDDEREQUESTFN | pfnRequest | Pointer to an optional function
  762.         to handle request requests for this item. If this 
  763.         function is not provided then the default action is to call
  764.         the topic generic request processor function if present.
  765.  
  766.     @parm PDDEPOKEFN | pfnPoke | Pointer to an optional function
  767.         to handle poke requests for this item.  If this 
  768.         function is not provided then the default action is to call
  769.         the topic generic poke processor function if present.
  770.  
  771.     @rdesc The return value is a pointer to a DDEITEMINFO structure
  772.         if the item is added or NULL if not.
  773.  
  774. */
  775.  
  776. PDDEITEMINFO AddDDEItem(LPSTR lpszTopic, 
  777.                         LPSTR lpszItem, 
  778.                         LPWORD lpFormatList,
  779.                         PDDEREQUESTFN lpReqFn, 
  780.                         PDDEPOKEFN lpPokeFn)
  781. {
  782.     PDDEITEMINFO pItem = NULL;
  783.     PDDETOPICINFO pTopic;
  784.  
  785.     //
  786.     // See if we have this topic already
  787.     //
  788.  
  789.     pTopic = FindTopicFromName(lpszTopic);
  790.  
  791.     if (!pTopic) {
  792.  
  793.         //
  794.         // We need to add this as a new topic
  795.         //
  796.  
  797.         pTopic = AddDDETopic(lpszTopic,
  798.                              NULL,
  799.                              NULL,
  800.                              NULL);
  801.     }
  802.  
  803.     if (!pTopic) return NULL;  // failed
  804.  
  805.     //
  806.     // See if we already have this item
  807.     //
  808.  
  809.     pItem = FindItemFromName(pTopic, lpszItem);
  810.  
  811.     if (pItem) {
  812.  
  813.         //
  814.         // Just update the info in it
  815.         //
  816.  
  817.         pItem->pfnRequest = lpReqFn;
  818.         pItem->pfnPoke = lpPokeFn;
  819.         pItem->pFormatList = lpFormatList;
  820.  
  821.     } else {
  822.  
  823.         //
  824.         // Create a new item
  825.         //
  826.  
  827.         pItem = _fcalloc(1, sizeof(DDEITEMINFO));
  828.         if (!pItem) return NULL;
  829.  
  830.         //
  831.         // Fill out the info
  832.         //
  833.  
  834.         pItem->pszItemName = lpszItem;
  835.         pItem->hszItemName = DdeCreateStringHandle(ServerInfo.dwDDEInstance,
  836.                                                    lpszItem,
  837.                                                    CP_WINANSI);
  838.         pItem->pTopic = pTopic;
  839.         pItem->pfnRequest = lpReqFn;
  840.         pItem->pfnPoke = lpPokeFn;
  841.         pItem->pFormatList = lpFormatList;
  842.  
  843.         //
  844.         // Add it to the existing item list for this topic
  845.         //
  846.  
  847.         pItem->pNext = pTopic->pItemList;
  848.         pTopic->pItemList = pItem;
  849.  
  850.     }
  851.  
  852.     return pItem;
  853. }
  854.  
  855. /*
  856.     @doc EXTERNAL
  857.  
  858.     @api BOOL | RemoveDDEItem | Remove an item from a topic.
  859.  
  860.     @parm LPSTR | pszTopic | Pointer to a string containing the
  861.         name of the topic to remove the item from.
  862.  
  863.     @parm LPSTR | pszItem | Pointer to a string containing the
  864.         name of the item to remove.
  865.  
  866.     @rdesc The return value is TRUE if the item is removed, FALSE
  867.         if not.
  868.  
  869.     @comm Removing all the items from a topic does not remove the topic.
  870.  
  871. */
  872.  
  873. BOOL RemoveDDEItem(LPSTR lpszTopic, LPSTR lpszItem)
  874. {
  875.     PDDETOPICINFO pTopic;
  876.     PDDEITEMINFO pItem, pPrevItem;
  877.  
  878.     //
  879.     // See if we have this topic
  880.     //
  881.  
  882.     pTopic = FindTopicFromName(lpszTopic);
  883.  
  884.     if (!pTopic) {
  885.         return FALSE;
  886.     }
  887.  
  888.     //
  889.     // Walk the topic item list looking for this item.
  890.     //
  891.  
  892.     pPrevItem = NULL;
  893.     pItem = pTopic->pItemList;
  894.     while (pItem) {
  895.  
  896.         if (lstrcmpi(pItem->pszItemName, lpszItem) == 0) {
  897.  
  898.             //
  899.             // Found it.  Unlink it from the list.
  900.             //
  901.  
  902.             if (pPrevItem) {
  903.  
  904.                 pPrevItem->pNext = pItem->pNext;
  905.  
  906.             } else {
  907.  
  908.                 pTopic->pItemList = pItem->pNext;
  909.  
  910.             }
  911.  
  912.             //
  913.             // Release its string handle
  914.             //
  915.  
  916.             DdeFreeStringHandle(ServerInfo.dwDDEInstance,
  917.                                 pItem->hszItemName);
  918.  
  919.             //
  920.             // Free the memory associated with it
  921.             //
  922.  
  923.             _ffree(pItem);
  924.  
  925.             return TRUE;
  926.         }
  927.  
  928.         pPrevItem = pItem;
  929.         pItem = pItem->pNext;
  930.     }
  931.  
  932.     //
  933.     // We don't have that one
  934.     //
  935.  
  936.     return FALSE;
  937. }
  938.  
  939. /*
  940.     @doc EXTERNAL
  941.  
  942.     @api PDDEEXECCMDFNINFO | AddDDEExecCmd | Add an execute command
  943.         processor to a topic.
  944.  
  945.     @parm LPSTR | pszTopic | Pointer to a string containing the
  946.         name of the topic to add the command to.
  947.  
  948.     @parm LPSTR | pszCmdName | Pointer to a string containing the
  949.         name of the command to add.
  950.  
  951.     @parm PDDEEXECCMDFN | pfnExecCmd | Pointer to a function to
  952.         handle the command request.
  953.  
  954.     @parm UINT | uiMinArgs | The minimum number of arguments which
  955.         are valid for this command.
  956.  
  957.     @parm UINT | uiMaxArgs | The maximum number of arguments which
  958.         are valid for this command.
  959.  
  960.     @rdesc The return value is a pointer to a DDEEXECCMDFNINFO 
  961.         structure if the command is added successfully, otherwise
  962.         it is NULL.
  963.  
  964.     @comm If the topic does not exist it will be created. If the
  965.         command already exists, the information will be updated.
  966.  
  967.     @cb BOOL | DdeExecFn <f DdeExecFn> is a placeholder for
  968.         the application supplied function name.  
  969.  
  970.     @parm HSZ | hszTopic | HSZ of the topic for which the command
  971.         is being issued.
  972.  
  973.     @parm HDDEDATA | hData | A DDE data handle to an object containing the
  974.         the command string.
  975.  
  976.     @rdesc The return value is TRUE if the command is processed with
  977.         no errors, and FALSE if not.
  978.  
  979.     
  980. */
  981.  
  982. PDDEEXECCMDFNINFO AddDDEExecCmd(LPSTR pszTopic, 
  983.                               LPSTR pszCmdName,
  984.                               PDDEEXECCMDFN pExecCmdFn,
  985.                               UINT uiMinArgs,
  986.                               UINT uiMaxArgs)
  987. {
  988.     PDDEEXECCMDFNINFO pCmd = NULL;
  989.     PDDEEXECCMDFNINFO pHead;
  990.     PDDETOPICINFO pTopic;
  991.  
  992.     //
  993.     // See if we have this topic already
  994.     //
  995.  
  996.     pTopic = FindTopicFromName(pszTopic);
  997.  
  998.     if (!pTopic) {
  999.  
  1000.         //
  1001.         // We need to add this as a new topic
  1002.         //
  1003.  
  1004.         pTopic = AddDDETopic(pszTopic,
  1005.                              NULL,
  1006.                              NULL,
  1007.                              NULL);
  1008.     }
  1009.  
  1010.     if (!pTopic) return NULL;  // failed
  1011.  
  1012.     //
  1013.     // See if we already have this command
  1014.     //
  1015.  
  1016.     pCmd = FindExecCmdFromName(pTopic, pszCmdName);
  1017.  
  1018.     if (pCmd) {
  1019.  
  1020.         //
  1021.         // Just update the info in it
  1022.         //
  1023.  
  1024.         pCmd->pFn = pExecCmdFn;
  1025.         pCmd->uiMinArgs = uiMinArgs;
  1026.         pCmd->uiMaxArgs = uiMaxArgs;
  1027.  
  1028.     } else {
  1029.  
  1030.         //
  1031.         // Create a new item
  1032.         //
  1033.  
  1034.         pCmd = _fcalloc(1, sizeof(DDEEXECCMDFNINFO));
  1035.         if (!pCmd) return NULL;
  1036.  
  1037.         //
  1038.         // Fill out the info
  1039.         //
  1040.  
  1041.         pCmd->pszCmdName = pszCmdName;
  1042.         pCmd->pTopic = pTopic;
  1043.         pCmd->pFn = pExecCmdFn;
  1044.         pCmd->uiMinArgs = uiMinArgs;
  1045.         pCmd->uiMaxArgs = uiMaxArgs;
  1046.  
  1047.         //
  1048.         // Add it to the existing cmd list for this topic
  1049.         //
  1050.  
  1051.         pHead = pTopic->pCmdList;
  1052.         pCmd->pNext = pTopic->pCmdList;
  1053.         pTopic->pCmdList = pCmd;
  1054.  
  1055.         //
  1056.         // If this was the first command added to the list,
  1057.         // add the 'Result' command too.  This supports the
  1058.         // Execute Control 1 protocol.
  1059.         //
  1060.  
  1061.         AddDDEExecCmd(pszTopic,
  1062.                       SZ_RESULT,
  1063.                       SysResultExecCmd,
  1064.                       1, 1);
  1065.  
  1066.     }
  1067.  
  1068.     return pCmd;
  1069. }
  1070.  
  1071. /*
  1072.     @doc EXTERNAL
  1073.  
  1074.     @api BOOL | RemoveDDEExecCmd | Remove a command from a topic.
  1075.  
  1076.     @parm LPSTR | pszTopic | Pointer to a string containing the
  1077.         name of the topic to remove the command from.
  1078.  
  1079.     @parm LPSTR | pszCmdName | Pointer to a string containing the
  1080.         name of the command to remove.
  1081.  
  1082.     @rdesc The return value is TRUE if the command is removed, FALSE
  1083.         if not.
  1084.  
  1085. */
  1086.  
  1087. BOOL RemoveDDEExecCmd(LPSTR pszTopic, LPSTR pszCmdName)
  1088. {
  1089.     PDDETOPICINFO pTopic;
  1090.     PDDEEXECCMDFNINFO pCmd, pPrevCmd;
  1091.  
  1092.     //
  1093.     // See if we have this topic
  1094.     //
  1095.  
  1096.     pTopic = FindTopicFromName(pszTopic);
  1097.  
  1098.     if (!pTopic) {
  1099.         return FALSE;
  1100.     }
  1101.  
  1102.     //
  1103.     // Walk the topic item list looking for this cmd.
  1104.     //
  1105.  
  1106.     pPrevCmd = NULL;
  1107.     pCmd = pTopic->pCmdList;
  1108.     while (pCmd) {
  1109.  
  1110.         if (lstrcmpi(pCmd->pszCmdName, pszCmdName) == 0) {
  1111.  
  1112.             //
  1113.             // Found it.  Unlink it from the list.
  1114.             //
  1115.  
  1116.             if (pPrevCmd) {
  1117.  
  1118.                 pPrevCmd->pNext = pCmd->pNext;
  1119.  
  1120.             } else {
  1121.  
  1122.                 pTopic->pCmdList = pCmd->pNext;
  1123.  
  1124.             }
  1125.  
  1126.             //
  1127.             // Free the memory associated with it
  1128.             //
  1129.  
  1130.             _ffree(pCmd);
  1131.  
  1132.             return TRUE;
  1133.         }
  1134.  
  1135.         pPrevCmd = pCmd;
  1136.         pCmd = pCmd->pNext;
  1137.     }
  1138.  
  1139.     //
  1140.     // We don't have that one
  1141.     //
  1142.  
  1143.     return FALSE;
  1144. }
  1145.  
  1146. /*
  1147.     @doc EXTERNAL
  1148.  
  1149.     @api LPSTR | GetCFNameFromId | Get the text name of a clipboard
  1150.         format from its id.
  1151.  
  1152.     @parm WORD | wFmt | The format tag to return the name for.
  1153.  
  1154.     @parm LPSTR | pBuf | Pointer to a buffer to receive the name.
  1155.  
  1156.     @parm int | iSize | The size of the buffer.
  1157.  
  1158.     @rdesc If found the return value is a pointer to the return string.
  1159.         If not found the return buffer contains an empty string.
  1160.  
  1161.     @comm This command supports both standard clipboard formats
  1162.         and registered ones.
  1163.  
  1164. */
  1165.  
  1166. LPSTR GetCFNameFromId(WORD wFmt, LPSTR lpBuf, int iSize)
  1167. {
  1168.     PCFTAGNAME pCTN;
  1169.  
  1170.     //
  1171.     // Try for a standard one first
  1172.     //
  1173.  
  1174.     pCTN = CFNames;
  1175.     while (pCTN->wFmt) {
  1176.         if (pCTN->wFmt == wFmt) {
  1177.             _fstrncpy(lpBuf, pCTN->pszName, iSize);
  1178.             return lpBuf;
  1179.         }
  1180.         pCTN++;
  1181.     }
  1182.  
  1183.     //
  1184.     // See if it's a registered one
  1185.     //
  1186.  
  1187.     if (GetClipboardFormatName(wFmt, lpBuf, iSize) == 0) {
  1188.  
  1189.         //
  1190.         // Nope.  It's unknown
  1191.         //
  1192.  
  1193.         *lpBuf = '\0';
  1194.     }
  1195.  
  1196.     return lpBuf;
  1197. }
  1198.  
  1199. /*
  1200.     @doc EXTERNAL
  1201.  
  1202.     @api WORD | GetCFIdFromName | Get a clipboard format id from its name.
  1203.  
  1204.     @parm LPSTR | pszName | Pointer to a string containing the name
  1205.         of the format.
  1206.  
  1207.     @rdesc The return value is either a standard format id or the
  1208.         result of registering the name.  The name supplied is always
  1209.         registerd if it is not one of the  standard ones.
  1210.  
  1211. */
  1212.  
  1213. WORD GetCFIdFromName(LPSTR pszName)
  1214. {
  1215.     PCFTAGNAME pCTN;
  1216.  
  1217.     //
  1218.     // Try for a standard one first
  1219.     //
  1220.  
  1221.     pCTN = CFNames;
  1222.     while (pCTN->wFmt) {
  1223.         if (lstrcmpi(pCTN->pszName, pszName) == 0) {
  1224.             return pCTN->wFmt;
  1225.         }
  1226.         pCTN++;
  1227.     }
  1228.  
  1229.     //
  1230.     // Register it
  1231.     //
  1232.  
  1233.     return RegisterClipboardFormat(pszName);
  1234. }
  1235.  
  1236. /*
  1237.     @doc EXTERNAL
  1238.  
  1239.     @api void | PostDDEAdvise | Post a DDE advise notice to DDEML.
  1240.  
  1241.     @parm PDDEITEMINFO | pItemInfo | Pointer to a DDEITEMINFO structure
  1242.         describing the item.
  1243.  
  1244.     @rdesc There is no return value.
  1245.  
  1246. */
  1247.  
  1248. void PostDDEAdvise(PDDEITEMINFO pItemInfo)
  1249. {
  1250.     if (pItemInfo && pItemInfo->pTopic) {
  1251.         DdePostAdvise(ServerInfo.dwDDEInstance,
  1252.                       pItemInfo->pTopic->hszTopicName,
  1253.                       pItemInfo->hszItemName);
  1254.     }
  1255. }
  1256.  
  1257. /*
  1258.     @doc INTERNAL
  1259.  
  1260.     @api HDDEDATA | StdDDECallback | Callback function from DDEML.
  1261.  
  1262.     @parm WORD | wType | The transaction type.
  1263.  
  1264.     @parm WORD | wFmt | The data format.
  1265.  
  1266.     @parm HCONV | hConv | Handle to the current conversation.
  1267.  
  1268.     @parm HSZ | hsz1 | Handle to a DDEML string.
  1269.  
  1270.     @parm HSZ | hsz2 | Handle to a DDEML string.
  1271.  
  1272.     @parm HDDEDATA | hData | Handle to a DDE data object.
  1273.  
  1274.     @parm DWORD | dwData1 | A DWORD parameter.
  1275.  
  1276.     @parm DWORD | dwData1 | A DWORD parameter.
  1277.  
  1278.     @rdesc The return value can be many things.  It can be a handle to
  1279.         a DDE data object, NULL or one of several flag values.  The return
  1280.         value depends on the type of transaction.
  1281.  
  1282. */
  1283.  
  1284. HDDEDATA FAR PASCAL __export StdDDECallback(WORD wType,
  1285.                                  WORD wFmt,
  1286.                                  HCONV hConv,
  1287.                                  HSZ hsz1,
  1288.                                  HSZ hsz2,
  1289.                                  HDDEDATA hData,
  1290.                                  DWORD dwData1,
  1291.                                  DWORD dwData2)
  1292. {
  1293.     HDDEDATA hDdeData = NULL;
  1294.  
  1295.     switch (wType) {
  1296.     case XTYP_CONNECT_CONFIRM:
  1297.  
  1298.         //
  1299.         // Add a new conversation to the list
  1300.         //
  1301.  
  1302.         AddConversation(hConv, hsz1);
  1303.         break;
  1304.  
  1305.     case XTYP_DISCONNECT:
  1306.  
  1307.         //
  1308.         // Remove a conversation from the list
  1309.         //
  1310.  
  1311.         RemoveConversation(hConv, hsz1);
  1312.         break;
  1313.  
  1314.     case XTYP_WILDCONNECT:
  1315.  
  1316.         //
  1317.         // We only support wild connects to either a NULL service
  1318.         // name or to the name of our own service.
  1319.         //
  1320.  
  1321.         if ((hsz2 == NULL)
  1322.         || !DdeCmpStringHandles(hsz2, ServerInfo.hszServiceName)) {
  1323.  
  1324.             return DoWildConnect(hsz1);
  1325.  
  1326.         }
  1327.         break;
  1328.  
  1329.         //
  1330.         // For all other messages we see if we want them here
  1331.         // and if not, they get passed on to the user callback
  1332.         // if one is defined.
  1333.         //
  1334.  
  1335.     case XTYP_ADVSTART:
  1336.     case XTYP_CONNECT:
  1337.     case XTYP_EXECUTE:
  1338.     case XTYP_REQUEST:
  1339.     case XTYP_ADVREQ:
  1340.     case XTYP_ADVDATA:
  1341.     case XTYP_POKE:
  1342.  
  1343.         //
  1344.         // Try and process them here first.
  1345.         //
  1346.  
  1347.         if (DoCallback(wType,
  1348.                        wFmt,
  1349.                        hConv,
  1350.                        hsz1,
  1351.                        hsz2,
  1352.                        hData,
  1353.                        &hDdeData)) {
  1354.  
  1355.             return hDdeData;
  1356.         }
  1357.  
  1358.         //
  1359.         // Fall Through to allow the custom callback a chance
  1360.         //
  1361.  
  1362.     default:
  1363.  
  1364.         if (ServerInfo.pfnCustomCallback != NULL) {
  1365.  
  1366.             return(ServerInfo.pfnCustomCallback(wType,  
  1367.                                                 wFmt, 
  1368.                                                 hConv, 
  1369.                                                 hsz1, 
  1370.                                                 hsz2, 
  1371.                                                 hData,
  1372.                                                 dwData1, 
  1373.                                                 dwData2));
  1374.         }
  1375.     }
  1376.  
  1377.     return (HDDEDATA) NULL;
  1378. }
  1379.  
  1380. /*
  1381.     @doc INTERNAL
  1382.  
  1383.     @api BOOL | DoCallback | Process a generic callback.
  1384.  
  1385.     @parm WORD | wType | The transaction type.
  1386.  
  1387.     @parm WORD | wFmt | The data format.
  1388.  
  1389.     @parm HCONV | hConv | Handle to the current conversation.
  1390.  
  1391.     @parm HSZ | hszTopic | HSZ for the current topic.
  1392.  
  1393.     @parm HSZ | hszItem | HSZ for the current item.
  1394.  
  1395.     @parm HDDEDATA | hData | Handle to a DDE data object.
  1396.  
  1397.     @parm HDDEDATA * | phReturnData | Pointer to a DDE data handle to
  1398.         be set with the data handle of the returned data.
  1399.  
  1400. */
  1401.  
  1402. BOOL DoCallback(WORD wType,
  1403.                 WORD wFmt,
  1404.                 HCONV hConv,
  1405.                 HSZ hszTopic,
  1406.                 HSZ hszItem,
  1407.                 HDDEDATA hData,
  1408.                 HDDEDATA *phReturnData)
  1409. {
  1410.     PDDETOPICINFO pTopic;
  1411.     PDDEITEMINFO pItem;
  1412.     LPWORD pFormat;
  1413.     PDDEPOKEFN pfnPoke;
  1414.     CONVINFO ci;
  1415.     PDDEREQUESTFN pfnRequest;
  1416.  
  1417.  
  1418.     //
  1419.     // See if we know the topic
  1420.     //
  1421.  
  1422.     pTopic = FindTopicFromHsz(hszTopic);
  1423.     if (!pTopic) {
  1424.  
  1425.         return FALSE;
  1426.  
  1427.     }
  1428.  
  1429.     //
  1430.     // See if this is an execute request for the topic
  1431.     //
  1432.  
  1433.     if (wType == XTYP_EXECUTE) {
  1434.  
  1435.         //
  1436.         // See if the user supplied a generic function to handle this
  1437.         // or, if not, see if there is a command table.  If so run
  1438.         // the parser over the command string.
  1439.         //
  1440.  
  1441.         if (pTopic->pfnExec) {
  1442.  
  1443.             //
  1444.             // Call the exec function to process it
  1445.             //
  1446.  
  1447.             if ((*pTopic->pfnExec)(hszTopic, hData)) {
  1448.  
  1449.                 *phReturnData = (HDDEDATA) DDE_FACK;
  1450.                 return TRUE;
  1451.  
  1452.             }
  1453.  
  1454.         } else if (pTopic->pCmdList) {
  1455.  
  1456.             //
  1457.             // Try to parse and execute the request
  1458.             //
  1459.  
  1460.             if (ProcessExecRequest(pTopic, hData)) {
  1461.  
  1462.                 *phReturnData = (HDDEDATA) DDE_FACK;
  1463.                 return TRUE;
  1464.  
  1465.             }
  1466.         }
  1467.  
  1468.         //
  1469.         // Either no function or it didn't get handled by the function
  1470.         //
  1471.  
  1472.         *phReturnData = (HDDEDATA) DDE_FNOTPROCESSED;
  1473.         return TRUE;
  1474.     }
  1475.  
  1476.     //
  1477.     // See if this is a connect request. Accept it if it is.
  1478.     //
  1479.  
  1480.     if (wType == XTYP_CONNECT) {
  1481.  
  1482.         *phReturnData = (HDDEDATA) TRUE;
  1483.         return TRUE;
  1484.     }
  1485.  
  1486.     //
  1487.     // For any other transaction we need to be sure this is an
  1488.     // item we support and in some cases, that the format requested
  1489.     // is supported for that item.
  1490.     //
  1491.  
  1492.     pItem = FindItemFromHsz(pTopic, hszItem);
  1493.     if (!pItem) {
  1494.  
  1495.         //
  1496.         // Not an item we support
  1497.         //
  1498.  
  1499.         return FALSE;
  1500.     }
  1501.  
  1502.     //
  1503.     // See if this is a supported format
  1504.     //
  1505.  
  1506.     pFormat = pItem->pFormatList;
  1507.     while (*pFormat) {
  1508.  
  1509.         if (*pFormat == wFmt) break;
  1510.         pFormat++;
  1511.     }
  1512.  
  1513.     if (! *pFormat) return FALSE; // not one we support
  1514.  
  1515.     //
  1516.     // Now just do whatever is required for each specific transaction
  1517.     //
  1518.  
  1519.     switch (wType) {
  1520.     case XTYP_ADVSTART:
  1521.  
  1522.         //
  1523.         // Start an advise request.  Topic/item and format are ok.
  1524.         //
  1525.  
  1526.         *phReturnData = (HDDEDATA) TRUE;
  1527.         break;
  1528.  
  1529.     case XTYP_POKE:
  1530.     case XTYP_ADVDATA:
  1531.  
  1532.         //
  1533.         // Some data for us. See if we have a poke function for 
  1534.         // this item or for this topic in general.
  1535.         //
  1536.  
  1537.         *phReturnData = (HDDEDATA) DDE_FNOTPROCESSED;
  1538.         pfnPoke = pItem->pfnPoke;
  1539.         if (!pfnPoke) pfnPoke = pTopic->pfnPoke;
  1540.         if (pfnPoke) {
  1541.  
  1542.             if ((*pfnPoke)(wFmt, hszTopic, hszItem, hData)) {
  1543.  
  1544.                 //
  1545.                 // Data at the server has changed.  See if we
  1546.                 // did this ourself (from a poke) or if it's from
  1547.                 // someone else.  If it came from elsewhere then post
  1548.                 // an advise notice of the change.
  1549.                 //
  1550.  
  1551.                 ci.cb = sizeof(CONVINFO);
  1552.                 if (DdeQueryConvInfo(hConv, (DWORD)QID_SYNC, &ci)) {
  1553.  
  1554.                     if (! (ci.wStatus & ST_ISSELF)) {
  1555.  
  1556.                         //
  1557.                         // It didn't come from us
  1558.                         //
  1559.  
  1560.                         DdePostAdvise(ServerInfo.dwDDEInstance,
  1561.                                       hszTopic,
  1562.                                       hszItem);
  1563.                     }
  1564.                 }
  1565.  
  1566.                 *phReturnData = (HDDEDATA) DDE_FACK; // say we took it
  1567.  
  1568.             }
  1569.         }
  1570.         break;
  1571.  
  1572.     case XTYP_ADVREQ:
  1573.     case XTYP_REQUEST:
  1574.  
  1575.         //
  1576.         // Attempt to start an advise or get the data on a topic/item
  1577.         // See if we have a request function for this item or
  1578.         // a generic one for the topic
  1579.         //
  1580.  
  1581.         pfnRequest = pItem->pfnRequest;
  1582.         if (!pfnRequest) pfnRequest = pTopic->pfnRequest;
  1583.         if (pfnRequest) {
  1584.  
  1585.             *phReturnData = (*pfnRequest)(wFmt, hszTopic, hszItem);
  1586.  
  1587.         } else {
  1588.  
  1589.             *phReturnData = (HDDEDATA) NULL;
  1590.  
  1591.         }
  1592.         break;
  1593.  
  1594.     default:
  1595.         break;
  1596.     }
  1597.  
  1598.     //
  1599.     // Say we processed the transaction in some way
  1600.     //
  1601.  
  1602.     return TRUE;
  1603.  
  1604. }
  1605.  
  1606. /*
  1607.     @doc INTERNAL
  1608.  
  1609.     @api HDDEDATA | SysReqTopics | Process a request for the topic list.
  1610.  
  1611.     @parm UINT | wFmt | The format of the data to be returned.
  1612.  
  1613.     @parm HSZ | hszTopic | The HSZ of the current topic.
  1614.  
  1615.     @parm HSZ | hszItem | The HSZ of the current item.
  1616.  
  1617.     @rdesc The return value is a DDE data handle to the data object
  1618.         containing the return data.
  1619.  
  1620.     @comm Typically the data is returned in CF_TEXT format as
  1621.         a tab delimited list of names.
  1622.  
  1623. */
  1624.  
  1625. HDDEDATA SysReqTopics(UINT wFmt, HSZ hszTopic, HSZ hszItem)
  1626. {
  1627.     HDDEDATA hData;
  1628.     PDDETOPICINFO pTopic;
  1629.     int cb, cbOffset;
  1630.  
  1631.     //
  1632.     // Create an empty data object to fill
  1633.     //
  1634.  
  1635.     hData = DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  1636.                                 NULL,
  1637.                                 0,
  1638.                                 0,
  1639.                                 hszItem,
  1640.                                 wFmt,
  1641.                                 0);
  1642.     pTopic = ServerInfo.pTopicList;
  1643.     cbOffset = 0;
  1644.     while (pTopic) {
  1645.  
  1646.         //
  1647.         // put in a tab delimiter unless this is the first item
  1648.         //
  1649.  
  1650.         if (cbOffset != 0) {
  1651.             DdeAddData(hData, SZ_TAB, lstrlen(SZ_TAB), cbOffset);
  1652.             cbOffset += lstrlen(SZ_TAB);
  1653.         }
  1654.  
  1655.         //
  1656.         // Copy the string name of the topic
  1657.         //
  1658.  
  1659.         cb = lstrlen(pTopic->pszTopicName);
  1660.         DdeAddData(hData, pTopic->pszTopicName, cb, cbOffset);
  1661.         cbOffset += cb;
  1662.  
  1663.         pTopic = pTopic->pNext;
  1664.  
  1665.     }
  1666.  
  1667.     //
  1668.     // Put a NULL on the end
  1669.     //
  1670.  
  1671.     DdeAddData(hData, "", 1, cbOffset);
  1672.  
  1673.     return hData;
  1674. }
  1675.  
  1676. /*
  1677.     @doc INTERNAL
  1678.  
  1679.     @api HDDEDATA | SysReqItems | Process a request for the system item list.
  1680.  
  1681.     @parm IUNT | wFmt | The format of the data to be returned.
  1682.  
  1683.     @parm HSZ | hszTopic | The HSZ of the current topic.
  1684.  
  1685.     @parm HSZ | hszItem | The HSZ of the current item.
  1686.  
  1687.     @rdesc The return value is a DDE data handle to the data object
  1688.         containing the return data.
  1689.  
  1690.     @comm Typically the data is returned in CF_TEXT format as
  1691.         a tab delimited list of names.
  1692.  
  1693. */
  1694.  
  1695. HDDEDATA SysReqItems(UINT wFmt, HSZ hszTopic, HSZ hszItem)
  1696. {
  1697.     HDDEDATA hData;
  1698.     PDDETOPICINFO pTopic;
  1699.     PDDEITEMINFO pItem;
  1700.     int cb, cbOffset;
  1701.  
  1702.     //
  1703.     // Find the system topic
  1704.     //
  1705.  
  1706.     pTopic = FindTopicFromHsz(hszTopic);
  1707.     if (!pTopic) return NULL;
  1708.  
  1709.     //
  1710.     // Create an empty data object to fill
  1711.     //
  1712.  
  1713.     hData = DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  1714.                                 NULL,
  1715.                                 0,
  1716.                                 0,
  1717.                                 hszItem,
  1718.                                 wFmt,
  1719.                                 0);
  1720.  
  1721.     //
  1722.     // Walk the item list
  1723.     //
  1724.  
  1725.     cbOffset = 0;
  1726.     pItem = pTopic->pItemList;
  1727.     while (pItem) {
  1728.  
  1729.         //
  1730.         // put in a tab delimiter unless this is the first item
  1731.         //
  1732.  
  1733.         if (cbOffset != 0) {
  1734.             DdeAddData(hData, SZ_TAB, lstrlen(SZ_TAB), cbOffset);
  1735.             cbOffset += lstrlen(SZ_TAB);
  1736.         }
  1737.  
  1738.         //
  1739.         // Copy the string name of the item
  1740.         //
  1741.  
  1742.         cb = lstrlen(pItem->pszItemName);
  1743.         DdeAddData(hData, pItem->pszItemName, cb, cbOffset);
  1744.         cbOffset += cb;
  1745.  
  1746.         pItem = pItem->pNext;
  1747.  
  1748.     }
  1749.  
  1750.     //
  1751.     // Put a NULL on the end
  1752.     //
  1753.  
  1754.     DdeAddData(hData, "", 1, cbOffset);
  1755.  
  1756.     return hData;
  1757. }
  1758.  
  1759. /*
  1760.     @doc INTERNAL
  1761.  
  1762.     @api HDDEDATA | SysReqFormats | Process a request for the
  1763.         system format list.
  1764.  
  1765.     @parm IUNT | wFmt | The format of the data to be returned.
  1766.  
  1767.     @parm HSZ | hszTopic | The HSZ of the current topic.
  1768.  
  1769.     @parm HSZ | hszItem | The HSZ of the current item.
  1770.  
  1771.     @rdesc The return value is a DDE data handle to the data object
  1772.         containing the return data.
  1773.  
  1774.     @comm Typically the data is returned in CF_TEXT format as
  1775.         a tab delimited list of names.  The format list is the union
  1776.         of all formats supported for all topics of the service.
  1777.  
  1778. */
  1779.  
  1780. HDDEDATA SysReqFormats(UINT wFmt, HSZ hszTopic, HSZ hszItem)
  1781. {
  1782.     HDDEDATA hData;
  1783.     PDDETOPICINFO pTopic;
  1784.     PDDEITEMINFO pItem;
  1785.     WORD wFormats[MAXFORMATS];
  1786.  
  1787.     wFormats[0] = NULL; // start with an empty list
  1788.  
  1789.     //
  1790.     // Create an empty data object to fill
  1791.     //
  1792.  
  1793.     hData = DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  1794.                                 NULL,
  1795.                                 0,
  1796.                                 0,
  1797.                                 hszItem,
  1798.                                 wFmt,
  1799.                                 0);
  1800.  
  1801.     //
  1802.     // Walk the topic list
  1803.     //
  1804.  
  1805.     pTopic = ServerInfo.pTopicList;
  1806.     while (pTopic) {
  1807.  
  1808.         //
  1809.         // Walk the item list for this topic
  1810.         //
  1811.     
  1812.         pItem = pTopic->pItemList;
  1813.         while (pItem) {
  1814.     
  1815.             //
  1816.             // Walk the formats list for this item
  1817.             // adding each one to the main list if unique
  1818.             //
  1819.     
  1820.             AddFormatsToList(wFormats, MAXFORMATS, pItem->pFormatList);
  1821.     
  1822.             pItem = pItem->pNext;
  1823.         }
  1824.  
  1825.         pTopic = pTopic->pNext;
  1826.     }
  1827.  
  1828.     //
  1829.     // Walk the table and build the text form
  1830.     //
  1831.  
  1832.     return MakeDataFromFormatList(wFormats, wFmt, hszItem);
  1833.  
  1834. }
  1835.  
  1836. /*
  1837.     @doc INTERNAL
  1838.  
  1839.     @api HDDEDATA | TopicReqFormats | Process a request for the
  1840.         list of formats supported for a given topic.
  1841.  
  1842.     @parm IUNT | wFmt | The format of the data to be returned.
  1843.  
  1844.     @parm HSZ | hszTopic | The HSZ of the current topic.
  1845.  
  1846.     @parm HSZ | hszItem | The HSZ of the current item.
  1847.  
  1848.     @rdesc The return value is a DDE data handle to the data object
  1849.         containing the return data.
  1850.  
  1851.     @comm Typically the data is returned in CF_TEXT format as
  1852.         a tab delimited list of names. The list is the union of
  1853.         all formats possible for this topic.
  1854.  
  1855. */
  1856.  
  1857. HDDEDATA TopicReqFormats(UINT wFmt, HSZ hszTopic, HSZ hszItem)
  1858. {
  1859.     HDDEDATA hData;
  1860.     PDDETOPICINFO pTopic;
  1861.     PDDEITEMINFO pItem;
  1862.     WORD wFormats[MAXFORMATS];
  1863.  
  1864.     //
  1865.     // Find the topic info
  1866.     //
  1867.  
  1868.     pTopic = FindTopicFromHsz(hszTopic);
  1869.     if (!pTopic) return NULL;
  1870.  
  1871.     wFormats[0] = NULL; // start with an empty list
  1872.  
  1873.     //
  1874.     // Create an empty data object to fill
  1875.     //
  1876.  
  1877.     hData = DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  1878.                                 NULL,
  1879.                                 0,
  1880.                                 0,
  1881.                                 hszItem,
  1882.                                 wFmt,
  1883.                                 0);
  1884.  
  1885.     //
  1886.     // Walk the item list for this topic
  1887.     //
  1888.  
  1889.     pItem = pTopic->pItemList;
  1890.     while (pItem) {
  1891.  
  1892.         //
  1893.         // Walk the formats list for this item
  1894.         // adding each one to the main list if unique
  1895.         //
  1896.  
  1897.         AddFormatsToList(wFormats, MAXFORMATS, pItem->pFormatList);
  1898.  
  1899.         pItem = pItem->pNext;
  1900.     }
  1901.  
  1902.     //
  1903.     // Walk the table and build the text form
  1904.     //
  1905.  
  1906.     return MakeDataFromFormatList(wFormats, wFmt, hszItem);
  1907.  
  1908. }
  1909.  
  1910. /*
  1911.     @doc INTERNAL
  1912.  
  1913.     @api HDDEDATA | SysReqProtocols | Process a request for the
  1914.         list of protocols supported by the server.
  1915.  
  1916.     @parm IUNT | wFmt | The format of the data to be returned.
  1917.  
  1918.     @parm HSZ | hszTopic | The HSZ of the current topic.
  1919.  
  1920.     @parm HSZ | hszItem | The HSZ of the current item.
  1921.  
  1922.     @rdesc The return value is a DDE data handle to the data object
  1923.         containing the return data.
  1924.  
  1925.     @comm Typically the data is returned in CF_TEXT format as
  1926.         a tab delimited list of names.  The default is to return
  1927.         the 'Execute Control 1' protocol.
  1928.  
  1929. */
  1930.  
  1931. HDDEDATA SysReqProtocols(UINT wFmt, HSZ hszTopic, HSZ hszItem)
  1932. {
  1933.     static char sz[] = SZ_EXECUTECONTROL1;
  1934.  
  1935.     return DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  1936.                                sz,
  1937.                                lstrlen(sz)+1,
  1938.                                0,
  1939.                                hszItem,
  1940.                                CF_TEXT,
  1941.                                0);
  1942. }
  1943.  
  1944. /*
  1945.     @doc INTERNAL
  1946.  
  1947.     @api HDDEDATA | Do WildConnect | Process a wild connect request.
  1948.  
  1949.     @parm HSZ | hszTopic | HSZ to the topic being connected to.
  1950.  
  1951.     @rdesc The return value is a DDE data handle to an object containing
  1952.         a list of topics which match the request.
  1953.  
  1954.     @comm Since we only support one service, this is much simpler.  
  1955.         If hszTopic is NULL we supply a list of all the topics we
  1956.         currently support.  If it's not NULL, we supply a list
  1957.         of topics (zero or one items) which match the requested topic.
  1958.         The list is terminated by a NULL entry.
  1959. */
  1960.  
  1961. HDDEDATA DoWildConnect(HSZ hszTopic)
  1962. {
  1963.     PDDETOPICINFO pTopic;
  1964.     int iTopics = 0;
  1965.     HDDEDATA hData;
  1966.     PHSZPAIR pHszPair;
  1967.  
  1968.     //
  1969.     // See how many topics we will be returning
  1970.     //
  1971.  
  1972.     if (hszTopic == NULL) {
  1973.  
  1974.         //
  1975.         // Count all the topics we have
  1976.         //
  1977.  
  1978.         pTopic = ServerInfo.pTopicList;
  1979.         while (pTopic) {
  1980.             iTopics++;
  1981.             pTopic = pTopic->pNext;
  1982.         }
  1983.  
  1984.     } else {
  1985.  
  1986.         //
  1987.         // See if we have this topic in our list
  1988.         //
  1989.  
  1990.         pTopic = ServerInfo.pTopicList;
  1991.         while (pTopic) {
  1992.             if (DdeCmpStringHandles(pTopic->hszTopicName, hszTopic) == 0) {
  1993.                 iTopics++;
  1994.                 break;
  1995.             }
  1996.             pTopic = pTopic->pNext;
  1997.         }
  1998.     }
  1999.  
  2000.     //
  2001.     // If we have no match or no topics at all, just return
  2002.     // NULL now to refuse the connect
  2003.     //
  2004.  
  2005.     if (!iTopics) return (HDDEDATA) NULL;
  2006.  
  2007.     //
  2008.     // Allocate a chunk of DDE data big enough for all the HSZPAIRS
  2009.     // we'll be sending back plus space for a NULL entry on the end
  2010.     //
  2011.  
  2012.     hData = DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  2013.                                 NULL,
  2014.                                 (iTopics + 1) * sizeof(HSZPAIR),
  2015.                                 0,
  2016.                                 NULL,
  2017.                                 0,
  2018.                                 0);
  2019.  
  2020.     //
  2021.     // Check we actually got it.
  2022.     //
  2023.  
  2024.     if (!hData) return (HDDEDATA) NULL;
  2025.  
  2026.     pHszPair = (PHSZPAIR) DdeAccessData(hData, NULL);
  2027.  
  2028.     //
  2029.     // Copy the topic data
  2030.     //
  2031.  
  2032.     if (hszTopic == NULL) {
  2033.  
  2034.         //
  2035.         // Copy all the topics we have (includes the system topic)
  2036.         //
  2037.  
  2038.         pTopic = ServerInfo.pTopicList;
  2039.         while (pTopic) {
  2040.  
  2041.             pHszPair->hszSvc = ServerInfo.hszServiceName;
  2042.             pHszPair->hszTopic = pTopic->hszTopicName;
  2043.  
  2044.             pHszPair++;
  2045.             pTopic = pTopic->pNext;
  2046.         }
  2047.  
  2048.     } else {
  2049.  
  2050.         //
  2051.         // Just copy the one topic asked for
  2052.         //
  2053.  
  2054.         pHszPair->hszSvc = ServerInfo.hszServiceName;
  2055.         pHszPair->hszTopic = hszTopic;
  2056.  
  2057.         pHszPair++;
  2058.  
  2059.     }
  2060.  
  2061.     //
  2062.     // Put the terminator on the end
  2063.     //
  2064.  
  2065.     pHszPair->hszSvc = NULL;
  2066.     pHszPair->hszTopic = NULL;
  2067.  
  2068.     //
  2069.     // Finished with the data block
  2070.     //
  2071.  
  2072.     DdeUnaccessData(hData);
  2073.  
  2074.     //
  2075.     // Return the block handle
  2076.     //
  2077.  
  2078.     return hData;
  2079. }
  2080.  
  2081. /*
  2082.     @doc INTERNAL
  2083.  
  2084.     @api HDDEDATA | MakeDataFromFormatList | Create a data item containing
  2085.         the names of all the formats supplied in a list.
  2086.  
  2087.     @parm LPWORD | pFmt | Pointer to a NULL terminated list of formats.
  2088.  
  2089.     @parm WORD | wFmt | The format to return the result in.
  2090.  
  2091.     @parm HSZ | hszItem | The item the list is beign prepared for.
  2092.  
  2093.     @rdesc The return value is a DDE data handle to a list of format names.
  2094.  
  2095. */
  2096.  
  2097. HDDEDATA MakeDataFromFormatList(LPWORD pFmt, WORD wFmt, HSZ hszItem)
  2098. {
  2099.     HDDEDATA hData;
  2100.     int cbOffset, cb;
  2101.     char buf[256];
  2102.  
  2103.     //
  2104.     // Create an empty data object to fill
  2105.     //
  2106.  
  2107.     hData = DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  2108.                                 NULL,
  2109.                                 0,
  2110.                                 0,
  2111.                                 hszItem,
  2112.                                 wFmt,
  2113.                                 0);
  2114.  
  2115.     //
  2116.     // Walk the format list
  2117.     //
  2118.  
  2119.     cbOffset = 0;
  2120.     while (*pFmt) {
  2121.  
  2122.         //
  2123.         // put in a tab delimiter unless this is the first item
  2124.         //
  2125.  
  2126.         if (cbOffset != 0) {
  2127.             DdeAddData(hData, SZ_TAB, lstrlen(SZ_TAB), cbOffset);
  2128.             cbOffset += lstrlen(SZ_TAB);
  2129.         }
  2130.  
  2131.         //
  2132.         // Copy the string name of the format
  2133.         //
  2134.  
  2135.         GetCFNameFromId(*pFmt, buf, sizeof(buf));
  2136.         cb = lstrlen(buf);
  2137.         DdeAddData(hData, buf, cb, cbOffset);
  2138.         cbOffset += cb;
  2139.  
  2140.         pFmt++;
  2141.  
  2142.     }
  2143.  
  2144.     //
  2145.     // Put a NULL on the end
  2146.     //
  2147.  
  2148.     DdeAddData(hData, "", 1, cbOffset);
  2149.  
  2150.     return hData;
  2151. }
  2152.  
  2153. /*
  2154.     @doc INTERNAL
  2155.  
  2156.     @api void | AddFormatsToList | Add a list of formats to a main
  2157.         list.
  2158.  
  2159.     @parm LPWORD | pMain | Pointer to the main list.
  2160.  
  2161.     @parm int | iMax | The amount of room left in the main list.
  2162.  
  2163.     @parm LPWORD | pList | Pointer to the list of formats to add.
  2164.  
  2165.     @rdesc There is no return value.
  2166.  
  2167.     @comm Each item is only added if it is not already present in the
  2168.         main list.
  2169.  
  2170. */
  2171.  
  2172. void AddFormatsToList(LPWORD pMain, int iMax, LPWORD pList)
  2173. {
  2174.     LPWORD pFmt, pLast;
  2175.     int iCount;
  2176.  
  2177.     if (!pMain || !pList) return;
  2178.  
  2179.     //
  2180.     // Count what we have to start with
  2181.     //
  2182.  
  2183.     iCount = 0;
  2184.     pLast = pMain;
  2185.     while (*pLast) {
  2186.         pLast++;
  2187.         iCount++;
  2188.     }
  2189.  
  2190.     //
  2191.     // Walk the new list ensuring we don't add the item if there
  2192.     // isn't room or we have it already.
  2193.     //
  2194.  
  2195.     while ((iCount < iMax) && *pList) {
  2196.  
  2197.         //
  2198.         // See if we have this one
  2199.         //
  2200.  
  2201.         pFmt = pMain;
  2202.         while (*pFmt) {
  2203.  
  2204.             if (*pFmt == *pList) {
  2205.  
  2206.                 //
  2207.                 // Already got this one
  2208.                 //
  2209.  
  2210.                 goto next_fmt; // I know.  I hate this too.
  2211.             }
  2212.  
  2213.             pFmt++;
  2214.         }
  2215.  
  2216.         //
  2217.         // Put it on the end of the list
  2218.         //
  2219.  
  2220.         *pLast++ = *pList;
  2221.         iCount++;
  2222.  
  2223. next_fmt:
  2224.  
  2225.         pList++;
  2226.     }
  2227.  
  2228.     //
  2229.     // Stick a null on the end to terminate the list
  2230.     //
  2231.  
  2232.     *pLast = NULL;
  2233. }
  2234.  
  2235. /*
  2236.     @doc INTERNAL
  2237.  
  2238.     @api PDDECONVINFO | FindConversation | Find the first occurence
  2239.         of a conversation on a given topic in the conversation list.
  2240.  
  2241.     @parm HSZ | hszTopic | HSZ to the topic being searched for.
  2242.  
  2243.     @rdesc The return value is a pointer to a DDECONVINFO structure
  2244.         describing the conversation.  If no conversation on this
  2245.         topic is found the return value is NULL.
  2246.  
  2247. */
  2248.  
  2249. static PDDECONVINFO FindConversation(HSZ hszTopic)
  2250. {
  2251.     PDDECONVINFO pCI;
  2252.  
  2253.     //
  2254.     // Try to find the info in the list
  2255.     //
  2256.  
  2257.     pCI = ServerInfo.pConvList;
  2258.     while (pCI) {
  2259.  
  2260.         if (DdeCmpStringHandles(pCI->hszTopicName, hszTopic) == 0) {
  2261.  
  2262.             return pCI;
  2263.  
  2264.         }
  2265.     
  2266.         pCI = pCI->pNext;
  2267.     }
  2268.  
  2269.     return NULL;
  2270. }
  2271.  
  2272. /*
  2273.     @doc INTERNAL
  2274.  
  2275.     @api BOOL | AddConversation | Add a conversation to the conversation list.
  2276.  
  2277.     @parm HCONV | hConv | Handle to the conversation to add.
  2278.  
  2279.     @parm HSZ | hszTopic | HSZ to the topic of the conversation.
  2280.  
  2281.     @rdesc The return value is TRUE if the item is added to the
  2282.         list, FALSE if not.
  2283.  
  2284. */
  2285.  
  2286. static BOOL AddConversation(HCONV hConv, HSZ hszTopic)
  2287. {
  2288.     PDDECONVINFO pCI;
  2289.  
  2290.     //
  2291.     // Allocate some memory for the info and fill it in
  2292.     //
  2293.  
  2294.     pCI = _fcalloc(1, sizeof(DDECONVINFO));
  2295.     if (!pCI) {
  2296.         return FALSE;
  2297.     }
  2298.  
  2299.     pCI->hConv = hConv;
  2300.     pCI->hszTopicName = hszTopic;
  2301.  
  2302.     //
  2303.     // Add it into the list
  2304.     //
  2305.  
  2306.     pCI->pNext = ServerInfo.pConvList;
  2307.     ServerInfo.pConvList = pCI;
  2308.  
  2309.     return TRUE;
  2310. }
  2311.  
  2312. /*
  2313.     @doc INTERNAL
  2314.  
  2315.     @api BOOL | RemoveConversation | Remove a conversation form the list.
  2316.  
  2317.     @parm HCONV | hConv | Handle of the conversation to remove.
  2318.  
  2319.     @parm HSZ | hszTopic | HSZ to the topic of the conversation.
  2320.  
  2321.     @rdesc The return value is TRUE if the conversation is
  2322.         removed, FALSE if not.
  2323.  
  2324. */
  2325.  
  2326. static BOOL RemoveConversation(HCONV hConv, HSZ hszTopic)
  2327. {
  2328.     PDDECONVINFO pCI, pPrevCI;
  2329.  
  2330.     //
  2331.     // Try to find the info in the list
  2332.     //
  2333.  
  2334.     pCI = ServerInfo.pConvList;
  2335.     pPrevCI = NULL;
  2336.     while (pCI) {
  2337.  
  2338.         if ((pCI->hConv == hConv)
  2339.         &&  (DdeCmpStringHandles(pCI->hszTopicName,hszTopic) == 0)) {
  2340.  
  2341.             //
  2342.             // Found it. Unlink it from the list
  2343.             //
  2344.  
  2345.             if (pPrevCI) {
  2346.  
  2347.                 pPrevCI->pNext = pCI->pNext;
  2348.  
  2349.             } else {
  2350.  
  2351.                 ServerInfo.pConvList = pCI->pNext;
  2352.  
  2353.             }
  2354.  
  2355.             //
  2356.             // Free the memory
  2357.             //
  2358.  
  2359.             _ffree(pCI);
  2360.  
  2361.             return TRUE;
  2362.  
  2363.         }
  2364.  
  2365.         pPrevCI = pCI;
  2366.         pCI = pCI->pNext;
  2367.     }
  2368.  
  2369.     //
  2370.     // Not in the list
  2371.     //
  2372.  
  2373.     return FALSE;
  2374. }
  2375.  
  2376. /////////////////////////////////////////////////////////////////////
  2377. //
  2378. // DDE Execute command parser
  2379. //
  2380. ////////////////////////////////////////////////////////////////////
  2381.  
  2382. /*
  2383.     @doc INTERNAL
  2384.  
  2385.     @api BOOL | ProcessExecRequest | Process a DDE execute command line.
  2386.  
  2387.     @parm PDDETOPICINFO | pTopic | Pointer to a DDETOPICINFO structure
  2388.         describing the topic.
  2389.  
  2390.     @parm HDDEDATA | hData | Handle to a DDE data item containing the
  2391.         execute command line string.
  2392.  
  2393.     @rdesc The return value is TRUE if no errors occur in parsing
  2394.         or executing the commands.  It is FALSE if any error occurs.
  2395.  
  2396.     @comm Support for the 'Execute Control 1' protocol is provided allowing
  2397.         return information to be sent back to the caller.
  2398.  
  2399. */
  2400.  
  2401. static BOOL ProcessExecRequest(PDDETOPICINFO pTopic, HDDEDATA hData)
  2402. {
  2403.     LPSTR pData;
  2404.     BOOL bResult = FALSE;
  2405.     POP OpTable[MAXOPS];
  2406.     PPOP ppOp, ppArg;
  2407.     UINT uiNargs;
  2408.     LPSTR pArgBuf = NULL;
  2409.     char szResult[MAXRESULTSIZE];
  2410.     PDDECONVINFO pCI;
  2411.  
  2412.     if (!hData) return FALSE;
  2413.     pData = (LPSTR) DdeAccessData(hData, NULL);
  2414.     if (!pData) return FALSE;
  2415.  
  2416.     //
  2417.     // Allocate some memory for the string argument buffer
  2418.     // Allocate a lot more than we might need so we can avoid
  2419.     // doing any space tests.
  2420.     //
  2421.  
  2422.     pArgBuf = _fcalloc(2, _fstrlen(pData));
  2423.     if (!pArgBuf) {
  2424.         goto PER_exit;
  2425.     }
  2426.  
  2427.     //
  2428.     // Get a pointer to the current conversation
  2429.     //
  2430.  
  2431.     pCI = FindConversation(pTopic->hszTopicName);
  2432.  
  2433.     //
  2434.     // Parse and execute each command in turn.
  2435.     // If an error occurs, set the error return string
  2436.     // and return FALSE.
  2437.     //
  2438.  
  2439.     while (pData && *pData) {
  2440.  
  2441.         //
  2442.         // Parse a single command
  2443.         //
  2444.  
  2445.         szResult[0] = '\0';
  2446.         bResult = ParseCmd(&pData,
  2447.                            pTopic,
  2448.                            szResult,
  2449.                            sizeof(szResult),
  2450.                            OpTable,
  2451.                            MAXOPS,
  2452.                            pArgBuf);
  2453.  
  2454.         if (!bResult) {
  2455.  
  2456.             //
  2457.             // See if the current conversation has a results
  2458.             // item to pass the error string back in
  2459.             //
  2460.  
  2461.             if (pCI && pCI->pResultItem) {
  2462.  
  2463.                 pCI->pResultItem->hData = 
  2464.                     DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  2465.                                         szResult,
  2466.                                         lstrlen(szResult)+1,
  2467.                                         0,
  2468.                                         pCI->pResultItem->hszItemName,
  2469.                                         CF_TEXT,
  2470.                                         0);
  2471.     
  2472.             }
  2473.  
  2474.             goto PER_exit;
  2475.         }
  2476.  
  2477.         //
  2478.         // Execute the op list
  2479.         //
  2480.  
  2481.         ppOp = OpTable;
  2482.  
  2483.         while (*ppOp) {
  2484.  
  2485.             //
  2486.             // Count the number of args
  2487.             //
  2488.  
  2489.             uiNargs = 0;
  2490.             ppArg = ppOp+1;
  2491.             while (*ppArg) {
  2492.                 uiNargs++;
  2493.                 ppArg++;
  2494.             }
  2495.  
  2496.             //
  2497.             // Call the function, passing the address of the first arg
  2498.             //
  2499.  
  2500.             ppArg = ppOp+1;
  2501.             szResult[0] = '\0';
  2502.             bResult = (*((PDDEEXECCMDFN)*ppOp))(pTopic,
  2503.                                                 szResult,
  2504.                                                 sizeof(szResult),
  2505.                                                 uiNargs,
  2506.                                                 (LPSTR FAR *)ppArg);
  2507.             //
  2508.             // See if the current conversation has a results
  2509.             // item to pass the result string back in
  2510.             //
  2511.  
  2512.             if (pCI && pCI->pResultItem) {
  2513.  
  2514.                 pCI->pResultItem->hData = 
  2515.                     DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  2516.                                         szResult,
  2517.                                         lstrlen(szResult)+1,
  2518.                                         0,
  2519.                                         pCI->pResultItem->hszItemName,
  2520.                                         CF_TEXT,
  2521.                                         0);
  2522.     
  2523.             }
  2524.     
  2525.             if (!bResult) {
  2526.                 goto PER_exit;
  2527.             }
  2528.     
  2529.             //
  2530.             // move on to the next function
  2531.             //
  2532.     
  2533.             while (*ppOp) ppOp++;
  2534.             ppOp++;
  2535.  
  2536.         }
  2537.  
  2538.     }
  2539.  
  2540.     //
  2541.     // if we get this far we're done
  2542.     //
  2543.  
  2544.     bResult = TRUE;
  2545.  
  2546. PER_exit:
  2547.  
  2548.     DdeUnaccessData(hData);
  2549.     if (pArgBuf) {
  2550.         _ffree(pArgBuf);
  2551.     }
  2552.  
  2553.     return bResult;
  2554. }
  2555.  
  2556. /*
  2557.     @doc INTERNAL
  2558.  
  2559.     @api BOOL | ParseCmd | Parse a single command.
  2560.  
  2561.     @parm LPSTR FAR * | ppszCmdLine | Pointer to a a pointer which addresses
  2562.         the command line to be parsed.
  2563.  
  2564.     @parm PDDETOPICINFO | pTopic | Pointer to the topic info structure.
  2565.  
  2566.     @parm LPSTR | pszError | Pointer to a buffer to receive the error string.
  2567.  
  2568.     @parm UINT | uiErrorSize | Size of the error return buffer.
  2569.  
  2570.     @parm PPOP | pOpTable | Pointer to a table in which the operator and
  2571.         operands are to be inserted.
  2572.  
  2573.     @parm UINT | uiNops | The size of the op table.
  2574.  
  2575.     @parm LPSTR | pArgBuf | Pointer to a buffer in which the arguments
  2576.         are to be constructed.
  2577.  
  2578.     @rdesc The return value is TRUE if there are no errors, else it is FALSE.
  2579.  
  2580.     @comm Error information may be set in the error return buffer.
  2581.  
  2582. */
  2583.  
  2584. static BOOL ParseCmd(LPSTR FAR *ppszCmdLine,
  2585.                      PDDETOPICINFO pTopic,
  2586.                      LPSTR pszError,
  2587.                      UINT uiErrorSize,
  2588.                      PPOP pOpTable,
  2589.                      UINT uiNops,
  2590.                      LPSTR pArgBuf)
  2591. {
  2592.     LPSTR pCmd, pArg;
  2593.     PPOP ppOp = pOpTable;
  2594.     PDDEEXECCMDFNINFO pExecFnInfo;
  2595.     UINT uiNargs;
  2596.     char cTerm;
  2597.  
  2598.     *ppOp = NULL;
  2599.     pCmd = SkipWhiteSpace(*ppszCmdLine);
  2600.  
  2601.     //
  2602.     // Scan for a command leadin
  2603.     //
  2604.  
  2605.     if (!ScanForChar('[', &pCmd)) {
  2606.         _fstrncpy(pszError,
  2607.                   "Missing '['",
  2608.                   uiErrorSize-1);
  2609.         return FALSE;
  2610.     }
  2611.  
  2612.     //
  2613.     // Scan for a valid command
  2614.     //
  2615.  
  2616.     pExecFnInfo = ScanForCommand(pTopic->pCmdList, &pCmd);
  2617.     if (!pExecFnInfo) {
  2618.         _fstrncpy(pszError,
  2619.                   "Invalid Command",
  2620.                   uiErrorSize-1);
  2621.         return FALSE;
  2622.     }
  2623.  
  2624.     //
  2625.     // Add the function pointer to the opcode list
  2626.     //
  2627.  
  2628.     *ppOp++ = pExecFnInfo->pFn;
  2629.  
  2630.     //
  2631.     // Scan for any arguments
  2632.     //
  2633.  
  2634.     uiNargs = 0;
  2635.     if (ScanForChar('(', &pCmd)) {
  2636.  
  2637.         //
  2638.         // Copy each argument to the op list
  2639.         //
  2640.  
  2641.         do {
  2642.  
  2643.             pArg = ScanForString(&pCmd, &cTerm, &pArgBuf);
  2644.  
  2645.             if (pArg) {
  2646.  
  2647.                 *ppOp++ = pArg;
  2648.                 uiNargs++;
  2649.  
  2650.             }
  2651.  
  2652.         } while (cTerm == ',');
  2653.  
  2654.         //
  2655.         // Confirm we have a terminating ) char
  2656.         //
  2657.  
  2658.         if ((cTerm != ')')
  2659.         && (!ScanForChar(')', &pCmd))) {
  2660.             _fstrncpy(pszError,
  2661.                       "Missing ')'",
  2662.                       uiErrorSize-1);
  2663.             return FALSE;
  2664.         }
  2665.  
  2666.     }   
  2667.        
  2668.     //
  2669.     // Test that we have a terminating ] char
  2670.     //
  2671.  
  2672.     if (!ScanForChar(']', &pCmd)) {
  2673.         _fstrncpy(pszError,
  2674.                   "Missing ']'",
  2675.                   uiErrorSize-1);
  2676.         return FALSE;
  2677.     }
  2678.  
  2679.     //
  2680.     // Test the number of args is correct
  2681.     //
  2682.  
  2683.     if (uiNargs < pExecFnInfo->uiMinArgs) {
  2684.         _fstrncpy(pszError,
  2685.                   "Too few arguments",
  2686.                   uiErrorSize-1);
  2687.         return FALSE;
  2688.     }
  2689.  
  2690.     if (uiNargs > pExecFnInfo->uiMaxArgs) {
  2691.         _fstrncpy(pszError,
  2692.                   "Too many arguments",
  2693.                   uiErrorSize-1);
  2694.         return FALSE;
  2695.     }
  2696.  
  2697.     //
  2698.     // Terminate this op list with a NULL
  2699.     //
  2700.  
  2701.     *ppOp++ = NULL;
  2702.  
  2703.     pCmd = SkipWhiteSpace(pCmd);
  2704.  
  2705.     //
  2706.     // Put a final NULL on the op list
  2707.     //
  2708.  
  2709.     *ppOp = NULL;
  2710.  
  2711.     //
  2712.     // Update the buffer pointer
  2713.     //
  2714.  
  2715.     *ppszCmdLine = pCmd;
  2716.  
  2717.     return TRUE;
  2718. }
  2719.  
  2720. /*
  2721.     @doc INTERNAL
  2722.  
  2723.     @api BOOL | SysResultExecCmd | Process a DDE Execute 'Result' command.
  2724.  
  2725.     @parm PDDETOPICINFO | pTopic | Pointer to a topic info structure.
  2726.  
  2727.     @parm LPSTR | pszResult | Pointer the the buffer to receive the 
  2728.         result string.
  2729.  
  2730.     @parm UINT | uiResultSize | Size of the return buffer.
  2731.  
  2732.     @parm UINT | uiNargs | Number of arguments in the argument list.
  2733.  
  2734.     @parm LPSTR FAR * | ppArgs | A list of pointers to the arguments.
  2735.  
  2736.     @rdesc The return value is TRUE if the command executes with no
  2737.         errors, otherwise it is FALSE.
  2738.  
  2739.     @comm This command creates a temporary item under the current topic
  2740.         which will contain the result of the next command to be executed.
  2741.  
  2742. */
  2743.  
  2744. static BOOL FAR SysResultExecCmd(PDDETOPICINFO pTopic,
  2745.                                  LPSTR pszResult, 
  2746.                                  UINT uiResultSize, 
  2747.                                  UINT uiNargs, 
  2748.                                  LPSTR FAR *ppArgs)
  2749. {
  2750.     PDDECONVINFO pCI;
  2751.  
  2752.     //
  2753.     // Find the conversation info
  2754.     //
  2755.  
  2756.     pCI = FindConversation(pTopic->hszTopicName);
  2757.     if (!pCI) return FALSE; // internal error
  2758.  
  2759.     //
  2760.     // See if we already have a temporary result item and
  2761.     // if we do, get rid of it
  2762.     //
  2763.  
  2764.     if (pCI->pResultItem) {
  2765.  
  2766.         RemoveDDEItem(pTopic->pszTopicName,
  2767.                       pCI->pResultItem->pszItemName);
  2768.     }
  2769.  
  2770.     //
  2771.     // Add a new temporary result item to the current conversation
  2772.     //
  2773.  
  2774.     pCI->pResultItem = AddDDEItem(pTopic->pszTopicName,
  2775.                                   ppArgs[0],
  2776.                                   SysFormatList,
  2777.                                   SysReqResultInfo,
  2778.                                   NULL);
  2779.  
  2780.     return TRUE;
  2781. }
  2782.  
  2783. /*
  2784.     @doc INTERNAL
  2785.  
  2786.     @api HDDEDATA | SysReqResultInfo | Return the 'result' 
  2787.         info for a given item and delete the item.
  2788.  
  2789.     @parm UINT | wFmt | The format to return the data in.
  2790.  
  2791.     @parm HSZ | hszTopic | HSZ value of the topic.
  2792.  
  2793.     @parm HSZ | hszItem | HSZ value of the item.
  2794.  
  2795.     @rdesc The return value is a DDE data handle to an object containing
  2796.         the return string.
  2797.  
  2798.     @comm The item is deleted after the data is returned.
  2799.  
  2800. */
  2801.  
  2802. HDDEDATA SysReqResultInfo(UINT wFmt, HSZ hszTopic, HSZ hszItem)
  2803. {
  2804.     HDDEDATA hData;
  2805.     PDDETOPICINFO pTopic;
  2806.     PDDEITEMINFO pItem;
  2807.  
  2808.     //
  2809.     // Find the item
  2810.     //
  2811.  
  2812.     pTopic = FindTopicFromHsz(hszTopic);
  2813.     pItem = FindItemFromHsz(pTopic, hszItem);
  2814.  
  2815.     //
  2816.     // See if it has any data to return
  2817.     //
  2818.  
  2819.     hData = pItem->hData;
  2820.  
  2821.     if (!hData) {
  2822.  
  2823.         //
  2824.         // Send back an empty string
  2825.         //
  2826.  
  2827.         hData = DdeCreateDataHandle(ServerInfo.dwDDEInstance,
  2828.                                    "",
  2829.                                    1,
  2830.                                    0,
  2831.                                    hszItem,
  2832.                                    CF_TEXT,
  2833.                                    wFmt);
  2834.     }
  2835.  
  2836.     //
  2837.     // Delete the item
  2838.     //
  2839.  
  2840.     RemoveDDEItem(pTopic->pszTopicName,
  2841.                   pItem->pszItemName);
  2842.  
  2843.     return hData;
  2844.  
  2845. }
  2846.  
  2847. /////////////////////////////////////////////////////////////////////
  2848. //
  2849. // Utility functions
  2850. //
  2851. /////////////////////////////////////////////////////////////////////
  2852.  
  2853. /*
  2854.     @doc INTERNAL
  2855.  
  2856.     @api LPSTR | SkipWhiteSpace | Skip over any white space characters.
  2857.  
  2858.     @parm LPSTR | pszString | Pointer to the current buffer position.
  2859.  
  2860.     @rdesc The return value is a pointer to the first non-white character.
  2861.  
  2862. */
  2863.  
  2864. static LPSTR SkipWhiteSpace(LPSTR pszString)
  2865. {
  2866.     while (pszString && *pszString && isspace(*pszString)) {
  2867.         pszString++;
  2868.     }
  2869.     return pszString;
  2870. }
  2871.  
  2872. /*
  2873.     @doc INTERNAL
  2874.  
  2875.     @api LPSTR | ScanForChar | Scan a buffer for a given character.
  2876.  
  2877.     @parm char | c | The character to look for.
  2878.  
  2879.     @parm LPSTR * | ppStr | Pointer to the current scan pointer.
  2880.  
  2881.     @rdesc The return value is a pointer to the character if found,
  2882.         NULL if not.
  2883.  
  2884.     @comm If the character is found the scan pointer is updated
  2885.         to point to the character following the one found.
  2886.  
  2887. */
  2888.  
  2889. static LPSTR ScanForChar(char c, LPSTR *ppStr)
  2890. {
  2891.     LPSTR p;
  2892.  
  2893.     p = SkipWhiteSpace(*ppStr);
  2894.     if (*p == c) {
  2895.         *ppStr = p+1;
  2896.         return p;
  2897.     }
  2898.     return NULL; // not found
  2899. }
  2900.  
  2901. /*
  2902.     @doc INTERNAL
  2903.  
  2904.     @api PDDEEXECCMDFNINFO | ScanForCommand | Scan for a valid comamnd.
  2905.  
  2906.     @parm PDDEEXECCMDFNINFO | pCmdInfo | Pointer to the current 
  2907.         command list.
  2908.  
  2909.     @parm LPSTR * | ppStr | Pointer to the current scan pointer.
  2910.  
  2911.     @rdesc The return value is a pointer to the command info if 
  2912.         found, NULL if not.
  2913.  
  2914.     @comm  If found, the scan pointer is updated.
  2915.  
  2916. */
  2917.  
  2918. static PDDEEXECCMDFNINFO ScanForCommand(PDDEEXECCMDFNINFO pCmdInfo, 
  2919.                                         LPSTR *ppStr)
  2920. {
  2921.     LPSTR p, pStart;
  2922.     char cSave;
  2923.  
  2924.  
  2925.     p = pStart = SkipWhiteSpace(*ppStr);
  2926.  
  2927.     //
  2928.     // Check the first char is alpha
  2929.     //
  2930.  
  2931.     if (!isalpha(*p)) {
  2932.         return NULL;
  2933.     }
  2934.  
  2935.     //
  2936.     // Collect alpha-num chars until we get to a non-alpha.
  2937.     //
  2938.  
  2939.     while (isalnum(*p)) p++;
  2940.  
  2941.     //
  2942.     // Terminate the source temporarily with a null
  2943.     //
  2944.  
  2945.     cSave = *p;
  2946.     *p = '\0';
  2947.  
  2948.     //
  2949.     // Search for a command that matches the name we have
  2950.     //
  2951.  
  2952.     while (pCmdInfo) {
  2953.  
  2954.         if (_fstricmp(pStart, pCmdInfo->pszCmdName) == 0) {
  2955.  
  2956.             //
  2957.             // Found it, so restore the delimter and
  2958.             // return the info pointer
  2959.             //
  2960.  
  2961.             *p = cSave;
  2962.             *ppStr = p;
  2963.             return pCmdInfo;
  2964.         }
  2965.         pCmdInfo = pCmdInfo->pNext;
  2966.     }
  2967.  
  2968.     //
  2969.     // Didn't find it, so restore delimiter and return
  2970.     //
  2971.  
  2972.     *p = cSave;
  2973.     return NULL; // not found
  2974. }
  2975.  
  2976. /*
  2977.     @doc INTERNAL
  2978.  
  2979.     @api LPSTR | ScanForString | Scan for a string.
  2980.  
  2981.     @parm LPSTR * | ppStr | Pointer to the current scan pointer.
  2982.  
  2983.     @parm LPSTR | pszTerm | Pointer to a location to receive the
  2984.         character which terminates the string.
  2985.  
  2986.     @parm LPSTR * | ppArgBuf | Pointer to the current arg buffer pointer.
  2987.  
  2988.     @rdesc The return value is a pointer to the string or NULL if
  2989.         an error occurs.
  2990.  
  2991.     @comm The scan pointer is updated to point past the string.  The
  2992.         arg buffer pointer is updated to point to the next free 
  2993.         character.
  2994. */
  2995.  
  2996. static LPSTR ScanForString(LPSTR *ppStr, LPSTR pszTerm, LPSTR *ppArgBuf)
  2997. {
  2998.     LPSTR pIn, pStart, pOut;
  2999.     BOOL bInQuotes = FALSE;
  3000.  
  3001.     pIn = SkipWhiteSpace(*ppStr);
  3002.     pOut = pStart = *ppArgBuf;
  3003.  
  3004.     //
  3005.     // See if this string is enclosed in quotes
  3006.     //
  3007.  
  3008.     if (*pIn == '"') {
  3009.         bInQuotes = TRUE;
  3010.         pIn++;
  3011.     }
  3012.  
  3013.     do {
  3014.  
  3015.         //
  3016.         // Test for the end of the string
  3017.         //
  3018.  
  3019.         if (bInQuotes) {
  3020.  
  3021.             if ((*pIn == '"') 
  3022.             && (*(pIn+1) != '"')) {
  3023.                 pIn++;  // skip over the quote
  3024.                 break;
  3025.             }
  3026.  
  3027.         } else {
  3028.  
  3029.             if (!IsValidStringChar(*pIn)) {
  3030.                 break;
  3031.             }
  3032.  
  3033.         }
  3034.  
  3035.         //
  3036.         // Test for an escape sequence
  3037.         //
  3038.  
  3039.         if ((*pIn == '"')
  3040.         && (*pIn == '"')) {
  3041.             pIn++; // skip the escaping first quote
  3042.         }
  3043.  
  3044.         if (*pIn == '\\') {
  3045.             pIn++; // skip the escaping backslash
  3046.         }
  3047.  
  3048.         //
  3049.         // Copy the char to the arg buffer
  3050.         //
  3051.  
  3052.         *pOut++ = *pIn++;
  3053.  
  3054.     } while(*pIn);
  3055.  
  3056.     *pOut++ = '\0';
  3057.  
  3058.     //
  3059.     // Set up the terminating char and update the scan pointer
  3060.     //
  3061.  
  3062.     *pszTerm = *pIn;
  3063.     if (*pIn) {
  3064.         *ppStr = pIn+1;
  3065.     } else {
  3066.         *ppStr = pIn;
  3067.     }
  3068.  
  3069.     //
  3070.     // Update the arg buffer to the next free bit
  3071.     //
  3072.  
  3073.     *ppArgBuf = pOut;
  3074.  
  3075.     return pStart;
  3076. }
  3077.  
  3078. /*
  3079.     @doc INTERNAL
  3080.  
  3081.     @api BOOL | IsValidStringChar | Test for a valid string character.
  3082.  
  3083.     @parm char | c | The character to test.
  3084.  
  3085.     @rdesc The return value is TRUE if the character is valid, FALSE
  3086.         if not.
  3087.  
  3088. */
  3089.  
  3090. static BOOL IsValidStringChar(char c)
  3091. {
  3092.     //
  3093.     // if it's 0-9 or a-z or A-Z it's ok
  3094.     //
  3095.  
  3096.     if (isalnum(c)) return TRUE;
  3097.  
  3098.     //
  3099.     // Test for other valid chars
  3100.     //
  3101.  
  3102.     switch (c) {
  3103.     case '_':
  3104.     case '$':
  3105.     case '.':
  3106.         return TRUE;
  3107.         break;
  3108.  
  3109.     default:
  3110.         break;
  3111.     }
  3112.  
  3113.     return FALSE;
  3114. }
  3115.